package com.csp.mingyue.common.oss.core;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.HttpMethod;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.CreateBucketRequest;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.csp.mingyue.common.oss.constant.OssConstant;
import com.csp.mingyue.common.oss.entity.UploadResult;
import com.csp.mingyue.common.oss.enums.AccessPolicyType;
import com.csp.mingyue.common.oss.enums.PolicyType;
import com.csp.mingyue.common.oss.exception.OssException;
import com.csp.mingyue.common.oss.support.OssProperties;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;

/**
 * Oss Client
 * </p>
 * 所有兼容S3协议的云厂商均支持(阿里云 腾讯云 七牛云 minio)
 *
 * @author njy
 * @date 2023/9/9 14:32
 */
public class OssClient {

	private final String configKey;

	private final OssProperties properties;

	private final AmazonS3 client;

	public OssClient(String configKey, OssProperties ossProperties) {
		this.configKey = configKey;
		this.properties = ossProperties;
		try {
			AwsClientBuilder.EndpointConfiguration endpointConfig = new AwsClientBuilder.EndpointConfiguration(
					properties.getEndpoint(), properties.getRegion());

			AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey());
			AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
			ClientConfiguration clientConfig = new ClientConfiguration();
			if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
				clientConfig.setProtocol(Protocol.HTTPS);
			}
			else {
				clientConfig.setProtocol(Protocol.HTTP);
			}
			AmazonS3ClientBuilder build = AmazonS3Client.builder().withEndpointConfiguration(endpointConfig)
					.withClientConfiguration(clientConfig).withCredentials(credentialsProvider)
					.disableChunkedEncoding();
			if (!StrUtil.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) {
				// minio 使用https限制使用域名访问 需要此配置 站点填域名
				build.enablePathStyleAccess();
			}
			this.client = build.build();

			createBucket();
		}
		catch (Exception e) {
			if (e instanceof OssException) {
				throw e;
			}
			throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
		}
	}

	public void createBucket() {
		try {
			String bucketName = properties.getBucketName();
			if (client.doesBucketExistV2(bucketName)) {
				return;
			}
			CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
			AccessPolicyType accessPolicy = getAccessPolicy();
			createBucketRequest.setCannedAcl(accessPolicy.getAcl());
			client.createBucket(createBucketRequest);
			client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
		}
		catch (Exception e) {
			throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
		}
	}

	public UploadResult upload(byte[] data, String path, String contentType) {
		return upload(new ByteArrayInputStream(data), path, contentType);
	}

	public UploadResult upload(InputStream inputStream, String path, String contentType) {
		if (!(inputStream instanceof ByteArrayInputStream)) {
			inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
		}
		try {
			ObjectMetadata metadata = new ObjectMetadata();
			metadata.setContentType(contentType);
			metadata.setContentLength(inputStream.available());
			PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream,
					metadata);
			// 设置上传对象的 Acl 为公共读
			putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
			client.putObject(putObjectRequest);
		}
		catch (Exception e) {
			throw new OssException("上传文件失败，请检查配置信息:[" + e.getMessage() + "]");
		}
		return UploadResult.builder().fileUrl(getUrl() + "/" + path).fileName(path).build();
	}

	public UploadResult upload(File file, String path) {
		try {
			PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file);
			// 设置上传对象的 Acl 为公共读
			putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
			client.putObject(putObjectRequest);
		}
		catch (Exception e) {
			throw new OssException("上传文件失败，请检查配置信息:[" + e.getMessage() + "]");
		}
		return UploadResult.builder().fileUrl(getUrl() + "/" + path).fileName(path).build();
	}

	public void delete(String path) {
		path = path.replace(getUrl() + "/", "");
		try {
			client.deleteObject(properties.getBucketName(), path);
		}
		catch (Exception e) {
			throw new OssException("删除文件失败，请检查配置信息:[" + e.getMessage() + "]");
		}
	}

	public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
		return upload(data, getPath(properties.getPrefix(), suffix), contentType);
	}

	public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
		return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
	}

	public UploadResult uploadSuffix(File file, String suffix) {
		return upload(file, getPath(properties.getPrefix(), suffix));
	}

	/**
	 * 获取文件元数据
	 * @param path 完整文件路径
	 */
	public ObjectMetadata getObjectMetadata(String path) {
		path = path.replace(getUrl() + "/", "");
		S3Object object = client.getObject(properties.getBucketName(), path);
		return object.getObjectMetadata();
	}

	public InputStream getObjectContent(String path) {
		path = path.replace(getUrl() + "/", "");
		S3Object object = client.getObject(properties.getBucketName(), path);
		return object.getObjectContent();
	}

	public String getUrl() {
		String domain = properties.getDomain();
		String endpoint = properties.getEndpoint();
		String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
		// 云服务商直接返回
		if (StrUtil.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
			if (StrUtil.isNotBlank(domain)) {
				return header + domain;
			}
			return header + properties.getBucketName() + "." + endpoint;
		}
		// minio 单独处理
		if (StrUtil.isNotBlank(domain)) {
			return header + domain + "/" + properties.getBucketName();
		}
		return header + endpoint + "/" + properties.getBucketName();
	}

	public String getPath(String prefix, String suffix) {
		// 生成uuid
		String uuid = IdUtil.fastSimpleUUID();
		// 文件路径
		String path = DateUtil.today() + "/" + uuid;
		if (StrUtil.isNotBlank(prefix)) {
			path = prefix + "/" + path;
		}
		return path + suffix;
	}

	public String getConfigKey() {
		return configKey;
	}

	/**
	 * 获取私有 URL 链接
	 * @param objectKey 对象KEY
	 * @param second 授权时间
	 */
	public String getPrivateUrl(String objectKey, Integer second) {
		GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(
				properties.getBucketName(), objectKey).withMethod(HttpMethod.GET)
						.withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
		URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
		return url.toString();
	}

	/**
	 * 检查配置是否相同
	 */
	public boolean checkPropertiesSame(OssProperties properties) {
		return this.properties.equals(properties);
	}

	/**
	 * 获取当前桶权限类型
	 * @return 当前桶权限类型 code
	 */
	public AccessPolicyType getAccessPolicy() {
		return AccessPolicyType.getByType(properties.getAccessPolicy());
	}

	private static String getPolicy(String bucketName, PolicyType policyType) {
		StringBuilder builder = new StringBuilder();
		builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n");
		if (policyType == PolicyType.WRITE) {
			builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n");
		}
		else if (policyType == PolicyType.READ_WRITE) {
			builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n");
		}
		else {
			builder.append("\"s3:GetBucketLocation\"\n");
		}
		builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
		builder.append(bucketName);
		builder.append("\"\n},\n");
		if (policyType == PolicyType.READ) {
			builder.append(
					"{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
			builder.append(bucketName);
			builder.append("\"\n},\n");
		}
		builder.append("{\n\"Action\": ");
		switch (policyType) {
			case WRITE:
				builder.append(
						"[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
				break;
			case READ_WRITE:
				builder.append(
						"[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
				break;
			default:
				builder.append("\"s3:GetObject\",\n");
				break;
		}
		builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
		builder.append(bucketName);
		builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n");
		return builder.toString();
	}

}
